package com.seed.application.service.update;

import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.query.QueryColumn;
import com.mybatisflex.core.query.QueryCondition;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.core.row.DbChain;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.row.RowKey;
import com.seed.application.service.remove.RemoveService;
import com.seed.application.service.save.SaveService;
import com.seed.application.validator.SaveValidator;
import com.seed.application.validator.UpdateValidator;
import com.seed.core.action.UpdateAction;
import com.seed.core.builder.declare.DeclaredBuilder;
import com.seed.core.enumeration.Cascade;
import com.seed.core.enumeration.PropertyType;
import com.seed.core.listener.OnSaveListener;
import com.seed.core.listener.OnUpdateListener;
import com.seed.core.pojo.Const;
import com.seed.core.pojo.seed.*;
import com.seed.core.query.pojo.Declared;
import com.seed.util.*;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class UpdateService {
    private final Seed seed;
    private final UpdateAction action;

    public UpdateService(Seed seed, UpdateAction action) {
        this.seed = seed;
        this.action = action;
    }

    public boolean exec(Row row) {
        Map<String, Declared> declareds = new DeclaredBuilder(seed, getDeclares()).build();
        update(seed, declareds, row);
        return true;
    }

    public boolean execBatch(List<Row> rows) {
        Map<String, Declared> declareds = new DeclaredBuilder(seed, getDeclares()).build();
        updateBatch(seed, declareds, rows);
        return true;
    }

    private List<String> getDeclares() {
        // 带路径的配置
        List<String> declares = action.getDeclares();
        if(declares != null) return declares;

        declares = seed.getDeclares();
        if(declares != null) return declares;

        return SeedKit.getDefaultDeclares(seed);
    }

    public static void update(Seed seed, Map<String, Declared> declareds, Row self) {
        for(Map.Entry<String, Declared> entry: declareds.entrySet()) {
            String crossOrColumn = entry.getKey();
            String fieldName = StrUtil.toCamelCase(crossOrColumn);
            Declared value = entry.getValue();
            PropertyType propertyType = value.getType();
            switch (propertyType) {
                case belongsTo -> {
                    BelongsTos belongsTo = (BelongsTos) value.getProperty();
                    Row target = (Row) self.remove(fieldName);
                    String selfField = StrUtil.toCamelCase(belongsTo.inferSelfColumn());
                    String targetField = StrUtil.toCamelCase(belongsTo.inferTargetColumn());
                    self.set(selfField, target == null ? null : target.get(targetField));
                }
                case hasOne -> updateHasOne(self, crossOrColumn, value);
                case hasMany -> updateHasMany(self, crossOrColumn, value);
                case habtm -> updateHabtm(self, crossOrColumn, value);
            }
        }

        List<? extends OnUpdateListener> listeners = seed.getOnUpdate();
        if(listeners != null) {
            for(OnUpdateListener listener: listeners) listener.onUpdate(self);
        }
        Row mainRow = RowKit.toUnderlineKeys(self);
        mainRow.getPrimaryKeys().add(RowKey.FLEX_ID);
        UpdateValidator.validate(seed, declareds, mainRow);
        Db.updateById(seed.getTableName(), mainRow);
    }

    public static void updateBatch(Seed seed, Map<String, Declared> declareds, List<Row> dataList) {
        if(dataList.isEmpty()) return;

        if(DeclareKit.isPlain(declareds)) {
            List<Row> rows = new ArrayList<>();
            for(Row row: dataList) {
                List<? extends OnSaveListener> listeners = seed.getOnSave();
                if(listeners != null) {
                    for(OnSaveListener listener: listeners) listener.onSave(row);
                }
                rows.add(row);
            }

            UpdateValidator.validate(seed, declareds, rows);
            Db.updateBatchById(seed.getTableName(), rows);
        } else {
            for (Row row: dataList) update(seed, declareds, row);
        }
    }

    private static void updateHasOne(Row self, String cross, Declared declared) {
        String fieldName = StrUtil.toCamelCase(cross);
        HasOnes hasOne = (HasOnes) declared.getProperty();
        Seed targetSeed = hasOne.targetSeed();
        String targetTableName = targetSeed.getTableName();
        String inferSelfColumn = hasOne.inferSelfColumn();
        String inferTargetColumn = hasOne.inferTargetColumn();
        String selfField = StrUtil.toCamelCase(inferSelfColumn);
        String targetField = StrUtil.toCamelCase(inferTargetColumn);
        Row target = (Row) self.remove(fieldName);

        QueryCondition queryCondition = HasOneKit.getQueryCondition(hasOne, self);
        Row one = DbChain.table(targetTableName).where(queryCondition).one();
        if(target == null) {
            removeOne(hasOne, one);
            return;
        }

        Object targetId = target.get(Const.ID);
        if(targetId == null) { // 新增加的数据
            removeOne(hasOne, one);

            target.set(targetField, self.get(selfField));
            SaveService.save(targetSeed, declared, target);
            return;
        }

        if(one == null) {
            target.set(targetField, self.get(selfField));
            update(targetSeed, declared, target);
            return;
        }

        if(targetId.equals(one.get(Const.ID))) { // 原来的数据
            target.set(targetField, self.get(selfField));
            update(targetSeed, declared, target);
            return;
        }

        removeOne(hasOne, one);
        target.set(targetField, self.get(selfField));
        update(targetSeed, declared, target);
    }

    private static void updateHasMany(Row self, String cross, Declared declared) {
        String fieldName = StrUtil.toCamelCase(cross);
        HasManys hasMany = (HasManys) declared.getProperty();
        Seed targetSeed = hasMany.targetSeed();
        String selfField = StrUtil.toCamelCase(hasMany.inferSelfColumn());
        String targetField = StrUtil.toCamelCase(hasMany.inferTargetColumn());
        String targetTableName = targetSeed.getTableName();

        Object targetObj = self.remove(fieldName);
        QueryCondition queryCondition = HasManyKit.getQueryCondition(hasMany, self);
        List<Row> many = DbChain.table(targetTableName).where(queryCondition).list();
        if(targetObj == null) {
            removeMany(hasMany, many);
            return;
        }

        Collection<Row> target;
        if(targetObj.getClass().isArray()) {
            target = List.of((Row[]) targetObj);
        } else if(targetObj instanceof Collection<?>) {
            target = (Collection<Row>) targetObj;
        } else {
            throw new RuntimeException(String.format("不支持的类型【%s】", targetObj.getClass()));
        }

        List<Row> adding = new ArrayList<>();
        for(Map<String, Object> map: target) {
            if(map.get(Const.ID) != null) continue;

            Row row = RowKit.toRow(map);
            row.getPrimaryKeys().add(RowKey.FLEX_ID);
            row.set(targetField, self.get(selfField));
            adding.add(row);
        }
        SaveService.saveBatch(targetSeed, declared, adding);

        List<Object> oldIds = many.stream().map(e -> e.get(Const.ID)).toList();
        List<Row> updating = new ArrayList<>();
        for(Map<String, Object> map: target) {
            if(!oldIds.contains(map.get(Const.ID))) continue;

            Row row = RowKit.toRow(map);
            row.set(targetField, self.get(selfField));
            updating.add(row);
        }
        updateBatch(targetSeed, declared, updating);

        List<Row> removing = new ArrayList<>();
        for(Map<String, Object> map: target) {
            if(map.get(Const.ID) != null && !oldIds.contains(map.get(Const.ID))) removing.add(RowKit.toRow(map));
        }
        removeMany(hasMany, removing);
    }

    private static void updateHabtm(Row self, String cross, Declared declared) {
        String fieldName = StrUtil.toCamelCase(cross);
        Habtms habtm = (Habtms) declared.getProperty();
        String through = habtm.throughTableName();
        String selfField = StrUtil.toCamelCase(habtm.inferSelfColumn());
        String targetField = StrUtil.toCamelCase(habtm.inferTargetColumn());
        String inferThroughSelfColumn = habtm.inferThroughSelfColumn();
        String inferThroughTargetColumn = habtm.inferThroughTargetColumn();
        String throughSelfField = StrUtil.toCamelCase(inferThroughSelfColumn);
        String throughTargetField = StrUtil.toCamelCase(inferThroughTargetColumn);
        Object targetObj = self.remove(fieldName);
        QueryCondition throughQueryCondition = HabtmKit.getThroughQueryCondition(habtm, self);
        List<Row> throughs = DbChain.table(through).where(throughQueryCondition).list();
        List<Object> throughTargetValues = throughs.stream().map(e -> e.get(throughTargetField)).toList();

        if(targetObj == null) {
            removeHabtm(habtm, throughs);
            return;
        }

        Collection<Row> target;
        if(targetObj.getClass().isArray()) {
            target = List.of((Row[]) targetObj);
        } else if(targetObj instanceof Collection<?>) {
            target = (Collection<Row>) targetObj;
        } else {
            throw new RuntimeException(String.format("不支持的类型【%s】", targetObj.getClass()));
        }

        List<Row> adding = new ArrayList<>();
        for(Row row: target) {
            Object targetValue = row.get(targetField);
            if(throughTargetValues.contains(targetValue)) continue;

            Row ret = Row.ofKey(RowKey.FLEX_ID);
            ret.set(throughSelfField, self.get(selfField));
            ret.set(throughTargetField, row.get(targetField));
            adding.add(ret);
        }
        if(!adding.isEmpty()) {
            SaveValidator.validate(habtm.getThroughSeed(), adding);
            Db.insertBatch(through, adding);
        }

        List<Object> targetValues = target.stream().map(e -> e.get(throughTargetField)).toList();
        List<Row> removing = new ArrayList<>();
        for(Row row: throughs) {
            Object throughTargetValue = row.get(throughTargetField);
            if(targetValues.contains(throughTargetValue)) continue;

            removing.add(row);
        }
        removeHabtm(habtm, removing);
    }

    /**
     * 删除hasOne关系数据
     * @param hasOne 关系
     * @param target 目标数据
     */
    private static void removeOne(HasOnes hasOne, Row target) {
        if(target == null) return;

        Serializable targetId = (Serializable) target.get(Const.ID);
        Cascade cascade = hasOne.getCascade();
        Seed targetSeed = hasOne.targetSeed();
        String targetTableName = targetSeed.getTableName();
        String inferTargetColumn = hasOne.inferTargetColumn();
        if(cascade == Cascade.force) {
            RemoveService.remove(targetSeed, targetId);
        } else {
            DbChain.table(targetTableName)
                    .set(inferTargetColumn, null)
                    .where(new QueryColumn(Const.ID).eq(targetId))
                    .update();
        }
    }

    private static void removeMany(HasManys hasMany, List<Row> many) {
        if (many == null || many.isEmpty()) return;

        List<Serializable> manyIds = many.stream().map(e -> (Serializable) e.get(Const.ID)).toList();
        Cascade cascade = hasMany.getCascade();
        Seed targetSeed = hasMany.targetSeed();
        String targetTableName = targetSeed.getTableName();
        String inferTargetColumn = hasMany.inferTargetColumn();
        if(cascade == Cascade.force) {
            RemoveService.removeBatch(targetSeed, manyIds);
        } else {
            DbChain.table(targetTableName)
                    .set(inferTargetColumn, null)
                    .where(new QueryColumn(Const.ID).in(manyIds))
                    .update();
        }
    }

    private static void removeHabtm(Habtms habtm, List<Row> throughs) {
        if(throughs.isEmpty()) return;

        List<Object> throughIds = throughs.stream().map(e -> e.get(Const.ID)).toList();
        Cascade cascade = habtm.getCascade();
        String through = habtm.throughTableName();
        String inferThroughSelfColumn = habtm.inferThroughSelfColumn();
        String inferThroughTargetColumn = habtm.inferThroughTargetColumn();
        if(cascade == Cascade.force) {
            Db.deleteByCondition(through, new QueryColumn(Const.ID).in(throughIds));
        } else {
            DbChain.table(through)
                    .set(inferThroughSelfColumn, null)
                    .set(inferThroughTargetColumn, null)
                    .where(new QueryColumn(Const.ID).in(throughIds))
                    .update();
        }
    }
}
